<?php

/*
 * Абстракный класс для классов деревьев
 * Он же - хранилище объектов-синглтонов этих классов
 */

namespace T4\TREE;

use T4\Exception;

abstract class ATree {
    
    // ---- Часть, отвечающая за синглтонное поведение ----
    
    /**
     * 
     * @var array Массив объектов-деревьев. Внутреннее кэширование и реализация синглтонов
     */
    protected static $instances = array();
        
    /**
     * Реализация синглтона через LSB
     */
    final public static function getInstance() {
	
	$class = get_called_class();
	
	if ( empty( self::$instances[$class] ) ) {
	    
		self::$instances[$class] = new $class;
		$tree = & self::$instances[$class];
		$tree->loadData();
		$tree->sortData();
		
	}

	return self::$instances[$class];
	
    }
    
    final protected function __construct() { }
    final protected function __clone() { }

    // ---- / Часть, отвечающая за синглтонное поведение ----

    // ---- Тут начинается собственно механика деревьев уже
    
    /**
     * Схема данных для дерева
     * Включает ссылки на модель данных, порядок сортировки и пр.
     * @var array Схема
     */
    static protected $schema;

    /**
     *
     * @var array Внутреннее представление упорядоченного дерева 
     */
    protected $tree = array(); 
    
    /**
     * Абстракная функция загрузки данных извне во внутреннюю структуру
     * Определяется в классах конкретных типов деревьев
     */
    final protected function loadData() {
	
	/**
	 * Определяем параметры дерева
	 * Модель, связанная с ним, 
	 * порядок сортировки нод внутри уровней
	 */
	$treeClass = get_called_class();
	
	$schema = &$treeClass::$schema;

	if ( !isset($schema['model']) || empty($schema['model']) )
	    throw new Exception('В классе дерева ' . $treeClass . ' не определена модель данных!');
	
	$modelClass = $schema['model'];
	
	if ( isset($schema['order']) && !empty($schema['order']) ) {
	    $order = $schema['order'];
	} else {
	    $order = $modelClass::PRIMARY_KEY;
	}
	
	/**
	 * Загружаем дерево
	 */
	$this->tree = $modelClass::findAll($order);
	
	/**
	 * Получаем класс для нод дерева
	 * Приводим все элементы к этому классу
	 */
	preg_match('/([a-zA-Z0-9]+)Tree$/', get_parent_class(get_called_class()), $matches);
	$treeType = $matches[1];
	$defaultNodeClass = '\\T4\\TREE\\' . $treeType . 'Node';

	foreach ( $this->tree as &$node ) {
	    $node = new $defaultNodeClass($this, $node);
	}
	
    }

    /**
     * Абстракная функция сортировки данных в упорядоченное дерево
     * Определяется в классах конкретных типов деревьев
     * Может оставаться пустой для некоторых классов деревьев
     */
    abstract protected function sortData();

    /**
     * Возвращает дерево целиком
     * @return array Массив, содержащий упорядоченное дерево 
     */
    final public function getTree() {
	return $this->tree;
    }
    

    /**
     * Поиск в дереве ноды по значению ее уникального поля
     * @param string  $field Поле, по которому идет поиск
     * @param string $value Значение этого поля
     * @return \T4\ANode Найденная нода или null в случае неуспеха поиска 
     */
    final public function findNodeByUniqueField($field, $value) {
	
	foreach ( $this->tree as $node ) {
	    if ( (string)$node->$field == $value )
		return $node;
	}
	
	return null;
	
    }


    /**
     * Поиск в дереве ноды по ее уникальному ID
     * @param int $id ID искомой ноды
     * @return \T4\ANode Найденная нода или null в случае неуспеха поиска 
     */
    final public function findNodeByID($id) {
	
	return $this->findNodeByUniqueField('id', $id);
	
    }
    
}